/*
 * http-vue-loader v1.4.2
 * https://github.com/FranckFreiburger/http-vue-loader
 *
 * Copyright (c) 2019 Franck Freiburger
 * Licensed under the MIT license
 */
(function umd(root, factory) {

    if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = factory(require('vue'));
    } else if (typeof define === "function" && define.amd) {
        define(['vue'], factory);
    } else {
        root.httpVueLoader = factory(root.Vue);
    }

})(this, function factory(Vue) {

    function httpVueLoader(url, options) {

        options = options || {};

        var name = options.name || url.replace(/^.+\//, '').replace(/\?.*$/, '').replace(/\.\w+$/, '');

        var template = null;
        var script = null;
        var styles = [];
        var el = null;

        return new Promise(function(resolve, reject) {

            loadVue(url)
                .then(function(descriptor) {

                    var module = {
                        exports: {}
                    };

                    var scriptExports = null;

                    if (descriptor.script) {

                        try {
                            scriptExports = loadScript(descriptor.script.content, url, module, options);
                        } catch (ex) {
                            if (options.warn) options.warn("Error in " + url + "\n" + ex.stack);
                            else console.error("Error in " + url + "\n" + ex.stack);
                            reject(ex);
                            return;
                        }
                    }

                    var component = Object.assign({
                        name: name,
                        template: descriptor.template ? descriptor.template.content : undefined
                    }, scriptExports);

                    if (descriptor.styles) {

                        var promises = descriptor.styles.map(function(style) {

                            return new Promise(function(resolveStyle, rejectStyle) {

                                if (style.lang) {

                                    if (!options.transform) {
                                        rejectStyle(new Error("lang \"" + style.lang + "\" is not supported. Use the \"transform\" option."));
                                        return;
                                    }

                                    options.transform(style.content, style.lang, url).then(function(css) {
                                        resolveStyle(css);
                                    }, function(err) {
                                        rejectStyle(err);
                                    });
                                } else {
                                    resolveStyle(style.content);
                                }
                            });
                        });

                        Promise.all(promises).then(function(cssList) {

                            cssList.forEach(function(css) {
                                addStyle(css, url);
                            });

                            resolve(component);
                        }, function(err) {
                            reject(err);
                        });
                    } else {
                        resolve(component);
                    }
                })
                .catch(reject);
        });
    }

    httpVueLoader.register = function(Vue, options) {

        Vue.component('http-vue-loader', {

            render: function(createElement) {
                return createElement(this.component);
            },

            components: {},

            props: {
                src: {
                    type: String,
                    required: true
                },
                name: {
                    type: String,
                    required: false
                }
            },

            data: function() {
                return {
                    component: null
                };
            },

            created: function() {

                var self = this;

                httpVueLoader(this.src, Object.assign({}, options, {
                    name: this.name
                })).then(function(component) {

                    self.component = component;
                }, function(err) {

                    if (options && options.warn) options.warn(err.stack);
                    else console.error(err.stack);
                });
            }
        });
    };

    function loadVue(url) {

        return new Promise(function(resolve, reject) {

            var xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.responseType = 'text';

            xhr.onload = function() {

                if (xhr.status < 200 || xhr.status >= 300) {
                    reject(new Error("load " + url + " failed: status " + xhr.status));
                    return;
                }

                try {
                    resolve(parseVue(xhr.responseText, url));
                } catch (ex) {
                    reject(ex);
                }
            };

            xhr.onerror = function() {
                reject(new Error("load " + url + " failed"));
            };

            xhr.send(null);
        });
    }

    function parseVue(content, path) {

        var descriptor = {
            template: null,
            script: null,
            styles: []
        };

        var parser = /<(template|script|style)(\s+[^>]*?)?>([\s\S]*?)<\/\1>/gmi;
        var match;

        var scriptFound = false;
        var templateFound = false;

        while ((match = parser.exec(content)) !== null) {

            var type = match[1];
            var attrs = match[2] || '';
            var content = match[3];

            if (type === 'template' && !templateFound) {

                descriptor.template = {
                    content: content,
                    attrs: parseAttrs(attrs)
                };
                templateFound = true;

            } else if (type === 'script' && !scriptFound) {

                descriptor.script = {
                    content: content,
                    attrs: parseAttrs(attrs)
                };
                scriptFound = true;

            } else if (type === 'style') {

                descriptor.styles.push({
                    content: content,
                    attrs: parseAttrs(attrs),
                    lang: (attrs.match(/lang\s*=\s*["']([^"']+)["']/) || [null, null])[1]
                });
            }
        }

        return descriptor;
    }

    function parseAttrs(attrsStr) {

        var attrs = {};
        var parser = /([a-z][a-z0-9_-]*)\s*=\s*["']([^"']+)["']/gmi;
        var match;

        while ((match = parser.exec(attrsStr)) !== null) {
            attrs[match[1]] = match[2];
        }

        return attrs;
    }

    function loadScript(content, url, module, options) {

        var exports = module.exports;

        var context = {
            module: module,
            exports: exports,
            require: function require(path) {

                var absolutePath;

                if (path.substr(0, 1) === '.') {
                    absolutePath = new URL(path, url).href;
                } else {
                    absolutePath = path;
                }

                if (options && options.require) {

                    var result = options.require(absolutePath);
                    if (result !== undefined) {
                        return result;
                    }
                }

                throw new Error("require is not supported for " + path);
            },
            __filename: url,
            __dirname: url.replace(/\/[^\/]+$/, '/')
        };

        if (options && options.beforeCompile) {
            content = options.beforeCompile(content, url);
        }

        var script = content.replace(/export\s+default/, 'module.exports =');

        try {
            new Function('require', 'module', 'exports', '__filename', '__dirname', script)(
                context.require,
                context.module,
                context.exports,
                context.__filename,
                context.__dirname
            );
        } catch (ex) {
            throw ex;
        }

        return module.exports;
    }

    var styleId = 0;

    function addStyle(css, url) {

        var style = document.createElement('style');
        style.type = 'text/css';
        style.setAttribute('data-vue-http', url);

        style.textContent = css;

        document.head.appendChild(style);
    }

    return httpVueLoader;
});
